package com.citywithincity.api;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.Job;
import org.quartz.SchedulerException;

import com.citywithincity.api.startup.AutoLoadPolicy;
import com.citywithincity.api.startup.ManualLoadPolicy;
import com.citywithincity.api.utils.ApiUtil;
import com.citywithincity.db.Db;
import com.citywithincity.models.Models;
import com.citywithincity.utils.ClassUtil;
import com.citywithincity.utils.ConfigReader;
import com.citywithincity.utils.CrossDomainUtil;
import com.damai.action.ActionMap;
import com.damai.action.ApiAction;
import com.damai.action.IAction;
import com.damai.action.aop.Interceptor;
import com.damai.action.aop.Invoker;
import com.damai.action.aop.impl.InvokerChain;
import com.damai.action.meta.ApiMetaData;
import com.damai.jobcenter.QuartzPlugin;

/**
 * Api filter
 * 
 * @author Randy
 *
 */
public class ApiFilter implements Filter,ActionMap {
	private int contextPathLength;
	private static final String SLASH = "/";

	private Map<String, IAction> routeMap;
	private Map<String, Invoker> routeBackMap;

	private static final String DEFAULT_ENCODING = "UTF-8";
	private static Log logger = LogFactory.getLog(ApiFilter.class);
	private String contextPath;

	private static ApiFilter filter;
	
	

	public static ApiFilter me() {
		return filter;
	}

	public synchronized void setBackup(String key, Invoker invoker) {

		routeBackMap.put(key, invoker);

	}

	public synchronized void resetBackup(String key) {
		Invoker orgInvoker = routeBackMap.get(key);

		if (orgInvoker == null) {
			return;
		}

		IAction action = ApiFilter.me().getActionMap().get(key);

		if (action instanceof ApiAction) {
			setInvoker((ApiAction) action, orgInvoker);
		}

		routeBackMap.remove(key);
	}

	public Invoker findInvoker(ApiAction action) {
		Invoker invoker = action.getInvoker();
		InvokerChain chain = null;
		while (invoker instanceof InvokerChain) {
			chain = (InvokerChain) invoker;
			invoker = chain.getInvoker();
		}
		return invoker;
	}

	public Invoker setInvoker(ApiAction action, Invoker newInvoker) {
		Invoker invoker = action.getInvoker();
		InvokerChain chain = null;
		while (invoker instanceof InvokerChain) {
			chain = (InvokerChain) invoker;
			invoker = chain.getInvoker();
		}

		if (chain != null) {
			chain.setInvoker(newInvoker);
		} else {
			action.setInvoker(newInvoker);
		}
		return invoker;
	}

	public synchronized void replaceAndBackup(String key, Invoker newInvoker) {

		IAction action = ApiFilter.me().getActionMap().get(key);

		if (action instanceof ApiAction) {

			Invoker orgInvoker = findInvoker((ApiAction) action);
			if (!routeBackMap.containsKey(key)) {
				routeBackMap.put(key, orgInvoker);
			}
			

			setInvoker((ApiAction) action, newInvoker);
		}

	}

	/**
	 * 获取相对路径
	 * 
	 * @return
	 */
	public String getPath() {
		return contextPath;
	}

	public String getContextPath() {
		return contextPath;
	}

	public String getKey(HttpServletRequest request) {
		return request.getRequestURI().substring(contextPathLength);
	}

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse resp = (HttpServletResponse) response;
		CrossDomainUtil.checkAndSetCrossDomain(req, resp);
		IAction action = getAction(req);
		if(action==null){
			logger.info("Cannot find "+req.getServletPath());
			chain.doFilter(request, response);
			return;
		}
		// crolldomain
		
		resp.setCharacterEncoding(DEFAULT_ENCODING);
		request.setCharacterEncoding(DEFAULT_ENCODING);
		
		action.doFilter(req, resp);
	}

	/**
	 * 是否可以处理这个请求, 全局判断，注意必须是运行效率很高的操作 不能涉及io操作，如涉及数据库等io操作，
	 * 请将这个判断放入具体的controller中判断
	 * 
	 * @param req
	 * @param target
	 * @param writer
	 * @return
	 */

	private Map<String, Class<?>> models = new HashMap<String, Class<?>>();
	private Map<String, Class<?>> controllers = new HashMap<String, Class<?>>();
	private List<Class<?>> entities = new ArrayList<Class<?>>();

	public void init(FilterConfig config) throws ServletException {
		try {
			logger.info("=======Begin to startup!==============");
			
			//Caster.to(null, int.class);
			
			long time = System.currentTimeMillis();
			ApiConfig.setName(config.getInitParameter("displayName"));
			filter = this;
			models.clear();
			controllers.clear();
			entities.clear();
			// 这里获取配置
			contextPath = config.getServletContext().getContextPath();
			contextPathLength = (contextPath == null || SLASH.equals(contextPath) ? 0 : contextPath.length());
			// 获取
			routeMap = new HashMap<String, IAction>();
			routeBackMap = new HashMap<String, Invoker>();

			String className = config.getInitParameter("className");

			Class<?> clazz = Class.forName(className);
			ApiConfig apiConfig = (ApiConfig) clazz.newInstance();
			//CtClassUtil.initClassPath();
			startup(apiConfig);
			logger.info(
					String.format("================================Startup success in [%d]ms========================!",
							System.currentTimeMillis() - time));

		} catch (Throwable e) {
			logger.fatal("Filter start failed!", e);
			throw new ServletException(e);
		} finally {
			Db.release();
		}

	}

	private StartupPolicy startupPolicy;
	private List<Interceptor> interceptors;
	
	private void startup(ApiConfig mainConfig) throws Exception {
		String configName = ConfigReader.getDefault().get("configName");
		
		
		boolean isRelease = "true".equals(ConfigReader.getDefault().get("release"));
		if(isRelease){
			ApiConfig.debug(false);
		}else{
			boolean isDebug = !"release".equals(configName);
			ApiConfig.debug(isDebug);
		}
		
		ApiConfig.out = ConfigReader.getDefault().getBoolean("output");
		ApiConfig.in = ConfigReader.getDefault().getBoolean("input");
		// 如果没有指定加载方式
		// 加载方式
		String startup = ConfigReader.getDefault().get("startup");
		if (startup == null || startup.equals("auto")) {
			startupPolicy = new AutoLoadPolicy();
		} else if (startup != null && startup.equals("manual")) {
			startupPolicy = new ManualLoadPolicy();
		} else {
			throw new RuntimeException("Config item ['startup'] must be one of ['auto','manual']!");
		}
		startupPolicy.startup(mainConfig, routeMap);
		interceptors = startupPolicy.getInterceptors();
		parseApi();
	}
	
	
	private Map<String, ApiMetaData> apiMap = new HashMap<String, ApiMetaData>();
	
	private void parseApi(){
		apiMap.clear();
		List<ApiMetaData> usingApi = ApiUtil.getUsingApi();
		for (ApiMetaData apiMetaData : usingApi) {
			apiMap.put(apiMetaData.getName(), apiMetaData);
		}
	}
	
	public ApiMetaData getApi(String name){
		return apiMap.get(name);
	}
	
	public Map<String, IAction> getActionMap() {
		return routeMap;
	}

	@Override
	public void destroy() {
		if (startupPolicy != null) {
			startupPolicy.destroy();
			startupPolicy = null;
		}

		routeMap.clear();
		models.clear();
		controllers.clear();
		entities.clear();
	}

	
	private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

	public IAction getAction(HttpServletRequest req) {
		String target = req.getRequestURI().substring(contextPathLength);
		if(target.contains(".")){
			return null;
		}
		try{
			lock.readLock().lock();
			IAction action = routeMap.get(target);
			if (action == null) {
				int last = target.lastIndexOf('/');
				if (last > 0) {
					String ntarget = target.substring(0, last);
					action = routeMap.get(ntarget);
					if (action != null) {
						String param = target.substring(last + 1);
						req.setAttribute("0", param);
					}
				}
			}
			return action;
		}finally{
			lock.readLock().unlock();
		}
		
	}

	
	private Map<String, IAction> moreMap = new HashMap<String, IAction>();
	
	//这里需要注意不是直接放到actionMap里面，而是间接的
	@Override
	public void putAll(Map<String, IAction> map) {
		try{
			lock.writeLock().lock();
			moreMap.putAll(map);
			for (IAction action : map.values()) {
				routeMap.put(action.getKey(), action);
			}
		}finally{
			lock.writeLock().unlock();
		}
	}

	@Override
	public void removeAll(Collection<String> keys) {
		try{
			lock.writeLock().lock();
			for (String key : keys) {
				if(moreMap.containsKey(key)){
					IAction action = moreMap.remove(key);
					routeMap.remove(action.getKey());
				}
			}
		}finally{
			lock.writeLock().unlock();
		}
	}

	@Override
	public List<Interceptor> getInterceptors() {
		return interceptors;
	}

	@Override
	public void startJob(Class<? extends Job> jobClass, String cron, String jobName, Map<String, Object> data)
			throws SchedulerException {
		QuartzPlugin.me().startJob(jobClass, cron,jobName,data);
	}

	@Override
	public void stopJob(String name) {
		try {
			QuartzPlugin.me().stopJob(name);
		} catch (SchedulerException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void setValue(String className, String fieldName, Object value) throws Exception {
		Class<?> clazz = Class.forName(className,true,ApiFilter.class.getClassLoader());
		Object model = Models.getModel(clazz);
		Field field = clazz.getField(fieldName);
		field.setAccessible(true);
		field.set(model, value);
	}

	@Override
	public void setStaticValue(String className, String fieldName, Object value) throws Exception {
		Class<?> clazz = Class.forName(className,true,ApiFilter.class.getClassLoader());
		ClassUtil.setStaticFieldValue(clazz, fieldName, value);
	}

}
